/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory;

import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;

/**
 * BeanFactory接口是访问spring bean容器的底层接口，他定义了作为一个bean容器需要提供的基本功能.
 * 他有很多扩展，比如{@link ListableBeanFactory}和{@link org.springframework.beans.factory.config.ConfigurableBeanFactory}
 * 这些子实现在基于该接口制定的功能的前提下实现了更多特定的功能.
 * <p>
 * BeanFactory作为一个容器，他的实现将会持有大量的bean定义，在维护保存这些bean定义对象的过程中，他会根据用户的配置来为每个bean定义都配置一
 * 个String类型的名称作为唯一标志。
 * <p>
 * 这些bean定义中包含了关于具体的bean对象的描述和约束，当从容器中获取一个bean对象时，容器可能会返回一个单例对象，也可能返回一个原型对象。
 * 具体返回的对象的类型，就取决于这些描述和约束，值得注意的一点是，这里的单例并不是常规意义上的单例对象，他的单例只作用在当前的容器内，即如果不
 * 是从当前容器获取的对象，那么他的实例和容器维护的对象并不是同一个，当然这个作用域的范围是可变的，可以根据自己的需求配置不同的作用域，比如在
 * web环境中，可以将范围精确到一次request或者一次session中。
 * <p>
 * 在spring项目内，BeanFactory被认为是所有Bean的注册中心，他集中进行了Bean的配置，这样做最大的好处就是我们不需要为了实例化单个对象而去
 * 查找/读取配置文件，毕竟读取文件的IO操作还是很浪费资源的。
 * <p>
 * 值得注意的是，关于DI的概念，对于常规的依赖注入[DI]，Spring更推荐使用setter/构造函数来配置应用程序的对象，而不是使用类似与BeanFactory查
 * 找的方式.(这里的应用程序对象可以理解为被定义为Bean的java对象实例）。
 * <p>
 * 在spring中，将通过setter/构造方法来配置对象的行为称为push，将通过BeanFactory查找的形式称之为pull，对于这两者的区别，其实还是比较抽象的。
 * 抽象的原因在于根本不知道两者的区别，因为这里面涉及到了bean定义和加载的过程，所以我看到这里的时候，很难理解两种模式的区别。
 * <p>
 * 在具体讨论两者的区别之前，首先需要明白一个概念：在spring中，依赖注入的功能主要使用过BeanFactory接口以及他的子接口来实现的。
 * 然后带着这个概念，再去探究push和pull的区别。
 * <p>
 * 从语义上来讲，push可以理解为推，pull可以理解为拉，看到这个词汇，很容易联想到消息队列中的pull模式和push模式。
 * 通常来讲在消息队列中push就是保持一个长连接，接受由服务端推送过来的消息，动作发起人是服务端，动作接受者是客户端，主动权掌握在服务端手里。
 * 而pull模式则是定时从服务端主动拉去数据，动作发起人是客户端，动作接受者是服务端，主动权掌握在客户端手里。
 * 值得注意的是，两者虽然动作发起人不一致，但其目的确是一致的。接下来我们就需要根据消息队列的概念去尝试理解spring中的pull和push。
 * <p>
 * 如果想理解spring里面的pull和push，首先就需要明确这里涉及到的主体是什么，前面提到过“在spring中，依赖注入的功能主要使用过BeanFactory接口
 * 以及他的子接口来实现的“，而BeanFactory实现的功能主要是作为spring容器，那么spring容器必然是主体之一，spring容器主要操作的又是java对象，
 * 那么这里涉及到的主体就可以很容易的确定下来了:spring容器,java对象。
 * <p>
 * 既然主体已经确定下来了，现在，唯一剩下的问题就是push和pull，这两个动作的发起人，只有了解这个我们才能真正的明白这两个模式的区别。
 * 对于通过setter方法和构造函数来完善java对象的属性，我们在常规的实现模式里面，通常都是手动的进行设值，很明显在这儿，我们是动作发起人，而java
 * 实例是动作接受者，因为对于我们来说，是我们将值push到了java对象内。这里java对象不变化，但是将我们替换为spring容器，那么这里面说到的push模
 * 式，应该就是spring容器将数据“推(push)”给java对象。
 * 所以，spring容器主动讲属性值赋给java对象就是“推(push)”模式。
 * 以此类推，java对象自己从spring容器获取属性值，就是“拉(pull)”模式、
 * <p>
 * 这么看来，两者最大的区别就在于动作发起人，那么为什么更推荐使用push模式，而不是pull模式呢？
 * 其原因最终还是要落在spring对bean的加载和实例化上，而关于bean的加载和实例化也是我阅读源码主要探究的问题。
 * <p>
 * 通常，BeanFactory会通过加载配置文件的形式来配置Bean定义，就比如常用的Spring*.xml配置文件，就是通过解析xml文件中的bean定义，然后在配置
 * 管理这些Bean。不过除了xml文件以外我们也可以通过其他方式来定义bean，比如properties文件，java注解，这个并没有强制限制。
 * 甚至我们可以直接通过java代码来创建对象而不经过容器，只不过spring更推荐我们使用DI来定义bean而已。
 * <p>
 * {@link ListableBeanFactory}和{@link HierarchicalBeanFactory}都是本类的子类，其中{@link ListableBeanFactory}提供了一个枚举
 * 所管理bean定义的能力,{@link HierarchicalBeanFactory}则提供了嵌套spring容器的能力。两者相比较，最大的区别就在于
 * {@link HierarchicalBeanFactory}中所有的操作都会检查他的父工厂。
 * 换而言之，假设在{@link HierarchicalBeanFactory}这种找不到一个bean定义的实例，那么这个操作就会传递给他的父工厂，一直到找到该bean或者
 * 执行到了最后一个父工厂中的该方法为止，根据这个概念，不难理解，子工厂会覆盖父工厂的同名bean。
 * <p>
 * 最后，也是最重要的，一个完整的Beaan工厂应该尽可能的按照顺序实现标准Bean生命周期接口，完整的初始化方法及其标准顺序是：
 *
 * <ol>
 * <li>BeanNameAware's {@code setBeanName}
 * <li>BeanClassLoaderAware's {@code setBeanClassLoader}
 * <li>BeanFactoryAware's {@code setBeanFactory}
 * <li>EnvironmentAware's {@code setEnvironment}
 * <li>EmbeddedValueResolverAware's {@code setEmbeddedValueResolver}
 * <li>ResourceLoaderAware's {@code setResourceLoader}
 * (only applicable when running in an application context)
 * <li>ApplicationEventPublisherAware's {@code setApplicationEventPublisher}
 * (only applicable when running in an application context)
 * <li>MessageSourceAware's {@code setMessageSource}
 * (only applicable when running in an application context)
 * <li>ApplicationContextAware's {@code setApplicationContext}
 * (only applicable when running in an application context)
 * <li>ServletContextAware's {@code setServletContext}
 * (only applicable when running in a web application context)
 * <li>{@code postProcessBeforeInitialization} methods of BeanPostProcessors
 * <li>InitializingBean's {@code afterPropertiesSet}
 * <li>a custom init-method definition
 * <li>{@code postProcessAfterInitialization} methods of BeanPostProcessors
 * </ol>
 *
 * <p>On shutdown of a bean factory, the following lifecycle methods apply:
 * <ol>
 * <li>{@code postProcessBeforeDestruction} methods of DestructionAwareBeanPostProcessors
 * <li>DisposableBean's {@code destroy}
 * <li>a custom destroy-method definition
 * </ol>
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Chris Beams
 * @see BeanNameAware#setBeanName
 * @see BeanClassLoaderAware#setBeanClassLoader
 * @see BeanFactoryAware#setBeanFactory
 * @see org.springframework.context.ResourceLoaderAware#setResourceLoader
 * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher
 * @see org.springframework.context.MessageSourceAware#setMessageSource
 * @see org.springframework.context.ApplicationContextAware#setApplicationContext
 * @see org.springframework.web.context.ServletContextAware#setServletContext
 * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization
 * @see InitializingBean#afterPropertiesSet
 * @see org.springframework.beans.factory.support.RootBeanDefinition#getInitMethodName
 * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization
 * @see DisposableBean#destroy
 * @see org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName
 * @since 13 April 2001
 */
public interface BeanFactory {

    /**
     * FACTORY_BEAN_PREFIX是一个标记，它主要用于在处理工厂bean对象的时候，区分用户想获取的是工厂bean本身还是工厂bean产生的对象实例。
     * 这里说到了工厂Bean，就不得不提到设计模式中的工厂模式，工厂模式是设计模式中比较简单，比较常用的一种创建型设计模式，被用来代替常规的
     * 使用new方式创建对象，他的应用场景主要是用来创建复杂对象，比如多构造参数的对象。
     * 工厂模式也分为很多种，在spring中提到工厂模式就需要接触一个类{@link FactoryBean},{@link FactoryBean}定义了一个spring工厂模式的
     * 模板，他是spring容器工厂模式的超级父接口，定义了spring容器中使用工厂模式常用的功能，而他的实现类就是这里提到的工厂bean。
     */
    String FACTORY_BEAN_PREFIX = "&";


    /**
     * 根据bean定义的唯一标识，来获取一个bean实例，因为在设计的时候，spring容器允许getBean系列的方法针对原型模式和单例模式进行一些特定的实
     * 现，所以，在基于这些特定实现的前提下，调用这个方法的时候，自然而然的就能够获取容器范围内的单例对象或者原型对象，
     * 关于具体的实现，通常都是由spring容器来保持一个bean定义的引用，之后在这个引用的基础上来进行额外的操作。
     * <p>
     * 同时这个方法的最底层实现一般都是根据bean定义的唯一标识来操作的，如果传入的参数是一个bean的别名，他还需要进行别名和唯一标识的转换工作。
     * <p>
     * 最后，如果在当前的spring容器中无法获取到指定的bean，还会访问他的上层容器，知道找到bean定义或者没有上层容器为止。
     *
     * @param name 要检索的bean名称
     * @return 一个bean实例
     * @throws NoSuchBeanDefinitionException 没有找到指定名称的bean定义
     * @throws BeansException                不能创建Bean。。
     */
    Object getBean(String name) throws BeansException;

    /**
     * 该方法返回一个单例实例或者原型实例。
     * <p>
     * 该方法是{@link #getBean(String)}方法的扩展，提供了类型限制。
     * <p>
     * 概述：
     * <p>
     * 简单的{@link #getBean(String)}方法可能会找到两个同名的的Bean定义，这个方法作为{@link #getBean(String)}
     * 方法的一个补充，提供了一个类型检查，来在一定程度上规避这种情况，如果查找的bean实例和指定类型不一致就会抛出
     * BeanNotOfRequiredTypeException异常。
     *
     * @param name         要检索的bean名称
     * @param requiredType 需要匹配的类型，可以是一个的类的超类或者接口。
     * @return 一个bean实例
     * @throws NoSuchBeanDefinitionException  找不到需要的bean定义
     * @throws BeanNotOfRequiredTypeException 找到了bean定义，但是bean类型不匹配。
     * @throws BeansException                 不能够创建bean
     */
    <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;

    /**
     * 该方法返回一个单例实例或者原型实例。
     * <p>
     * 该方法是{@link #getBean(String)}方法的扩展，提供了覆盖原始构造参数({@link FactoryBean}实例的方法参数)。
     *
     * @param name 要检索的bean名称
     * @param args 创建Bean实例时需要使用的参数，需要注意的是，这个参数只有在创建bean实例的时候才会用，如果已经创建了bean实例，该参数不生
     *             效。
     * @return 一个Bean实例
     * @throws NoSuchBeanDefinitionException 没有找到bean定义
     * @throws BeanDefinitionStoreException  指定了参数，但是创建的bean实例不是一个原型实例。
     * @throws BeansException                不能够创建指定的bean
     * @since 2.5
     */
    Object getBean(String name, Object... args) throws BeansException;

    /**
     * 返回指定类型的bean实例。
     * <p>
     * 对于这个方法给出两种实现建议，{@link ListableBeanFactory}的按照类型查找，或者将类型转换成bean名称之后继续查找。
     * <p>
     * 对于那些跨越多个bean集合的查找建议使用{@link ListableBeanFactory}或者{@link BeanFactoryUtils}来实现。
     *
     * @param requiredType 需要获取bean定义的类型，可以是一个接口或者超类，但是不能是{@code null}。
     * @return 指定类型的bean实例。
     * @throws NoSuchBeanDefinitionException   没有找到bean定义
     * @throws NoUniqueBeanDefinitionException 找到了多个bean定义。
     * @throws BeansException                  不能够创建bean定义。
     * @see ListableBeanFactory
     * @since 3.0
     */
    <T> T getBean(Class<T> requiredType) throws BeansException;

    /**
     * 返回一个原型或者单例的Bean实例。
     * <p>
     * 该方法作为{@link #getBean(Class)}的一种补充，允许指定创建bean实例时使用的构造参数({@link FactoryBean}实例的方法参数).
     * <p>
     *
     * @param requiredType 要获取bean定义的类型，可以是一个接口或者超类，但是不能是{@code null}。
     * @param args         创建Bean实例时需要使用的参数，需要注意的是，这个参数只有在创建bean实例的时候才会用，如果已经创建了bean实例，该参数不生
     *                     效。
     * @return 一个Bean实例
     * @throws NoSuchBeanDefinitionException 没有找到bean定义
     * @throws BeanDefinitionStoreException  指定了参数，但是创建的bean实例不是一个原型实例。
     * @throws BeansException                不能够创建bean
     * @since 4.1
     */
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;


    /**
     * 判断bean容器内是否持有指定名称的bean定义，或者是否通过其他方式往spring容器中注册了一个单例bean实例。
     * <p>
     * 如果指定的bean名称是一个别名，在查找之前会先把别名转换成真正的bean名称。
     * <p>
     * 如果当前spring容器嵌套了多层，在当前容器查找不到bean定义的时候，还会去其父级容器中查找，直到找到了或者没有父级容器了为止。
     * <p>
     * 如果这个方法返回了{@code true}，仅仅只能表示它能够找到匹配的bean定义，但是他不会管这些bean是懒加载的还是立即加载的，也不管这些Bean
     * 定义是具体的还是抽象的，甚至是不是在存在于当前容器范围内，他都不理会，他只是表示：“哦，我找到了这个bean定义”，仅此而已。
     * <p>
     * 所以，有时候这个方法返回了{@code true}，我们调用{@link #getBean}方法的时候依然可能会出问题。
     *
     * @param name 要查找的bean名称
     * @return 是否存在指定名称的bean
     */
    boolean containsBean(String name);

    /**
     * 这个方法用来判断指定名称的bean实例是不是一个单例对象，换而言之就是多次通过{@link #getBean}获取的Bean实例的时候是不是返回的总是同一
     * 个实例，如果是的话，那就返回{@code true}.
     * <p>
     * 不过，就算这个方法返回{@code false}，也不能确定这个方法返回的是原型对象，他只能表示他是一个非单例对象，如果要明确的确定一个对象是不是
     * 原型实例，那还是使用{@link #isPrototype(String)}方法来检验比较稳妥一些。
     * <p>
     * 对于这个方法的实现，如果他接收的参数是一个bean的别名，他通常会讲别名转换成bean的原始名称，然后继续在继续处理。在判断bean定义类型的过
     * 程中，如果不能够找到这个bean定义，那么他还会去他的父级容器寻找。
     *
     * @param name 要判断bean的名称
     * @return 是否是一个单例bean
     * @throws NoSuchBeanDefinitionException 没有找到指定名称的bean定义
     * @see #getBean
     * @see #isPrototype
     */
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    /**
     * 判断指定名称的bean是不是一个原型实例，换而言之就是多次通过{@link #getBean}方法总能获取不同的bean实例。
     * <p>
     * 需要注意的是：
     * 就算这个方法返回了{@code false}，也不意味着这个bean就是一个单例bean对象，他仅仅只能表示这个对象不是原型对象而已，如果要明确的判断一
     * 个bean是否是单例实例，还是老老实实的使用{@link #isSingleton(String)}校验比较好。
     * <p>
     * 对于这个方法的实现，如果他接收的参数是一个bean的别名，他通常会讲别名转换成bean的原始名称，然后继续在继续处理。在判断bean定义类型的过
     * 程中，如果不能够找到这个bean定义，那么他还会去他的父级容器寻找。
     *
     * @param name 要判断bean的名称
     * @return 是否是一个原型实例
     * @throws NoSuchBeanDefinitionException 没有找到指定名称的bean定义
     * @see #getBean
     * @see #isSingleton
     * @since 2.0.3
     */
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    /**
     * 检查指定名称的bean的实例是不是指定的类型。
     * <p>
     * 对于这个方法的实现，如果他接收的参数是一个bean的别名，他通常会讲别名转换成bean的原始名称，然后继续在继续处理。在判断bean定义类型的过
     * 程中，如果不能够找到这个bean定义，那么他还会去他的父级容器寻找。
     *
     * @param name        要判断bean的名称
     * @param typeToMatch 要匹配的类型
     * @return 如果类型匹配返回 {@code true},如果不匹配或者不确定返回{@code false};
     * @throws NoSuchBeanDefinitionException 没有找到指定名称的bean定义
     * @see #getBean
     * @see #getType
     * @since 4.2
     */
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

    /**
     * 检查指定名称的bean的实例是不是指定的类型。
     * <p>
     * 对于这个方法的实现，如果他接收的参数是一个bean的别名，他通常会讲别名转换成bean的原始名称，然后继续在继续处理。在判断bean定义类型的过
     * 程中，如果不能够找到这个bean定义，那么他还会去他的父级容器寻找。
     *
     * @param name        要判断bean的名称
     * @param typeToMatch 要匹配的类型
     * @return {@code true} 如果类型匹配返回 {@code true},如果不匹配或者不确定返回{@code false};
     * @throws NoSuchBeanDefinitionException 没有找到指定名称的bean定义
     * @see #getBean
     * @see #getType
     * @since 2.0.1
     */
    boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    /**
     * 获取指定名称bean的类型，如果目标bean是{@link FactoryBean}的实例，返回的是其创建的bean实例类型。
     * <p>
     * 对于这个方法的实现，如果他接收的参数是一个bean的别名，他通常会讲别名转换成bean的原始名称，然后继续在继续处理。在判断bean定义类型的过
     * 程中，如果不能够找到这个bean定义，那么他还会去他的父级容器寻找。
     *
     * @param name 要判断bean的名称
     * @return 指定bean的类型，如果不确定可以返回{@code null}
     * @throws NoSuchBeanDefinitionException 没有找到指定名称的bean定义
     * @see #getBean
     * @see #isTypeMatch
     * @since 1.1.2
     */
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    /**
     * 返回指定名称的bean的所有别名。
     * <p>
     * 当调用{@link #getBean}，入参这些别名都能获取到相同的bean的实例。
     * <p>
     * 在返回结果里面，原始的bean名称将会作为数组的第一个元素返回。
     * <p>
     * 在查找的过程中如果找不到bean，还会查找其父容器。
     *
     * @param name 要检查别名的bean名称。
     * @return 别名集合，如果没有就返回 {@code null}
     * @see #getBean
     */
    String[] getAliases(String name);

}
